import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;
import java.util.Random;


/**
 * @author Jedrzej Ginter, Adam Pociejowski
 * @version 1.0
 */
public class BadugiBot extends Thread {
	private Socket botSocket;
	private DataOutputStream writer;
	private DataInputStream reader;
		
	private int botID = 0, 
			botChips = 0, 
			botChipsOnStart = 0, 
			botBet = 0, 
			botTotalBet = 0, 
			botSuggestedMaxBet = 0,
			gameCurrentRound = 1,
			gameMaxBet = 0;
	
	private double botChance = 0;
	
	private boolean isGameOpen = false,
			botMovesEnabled = true,
			connectionOk = false;
	
	private boolean[] botPossibleMoves;
	
	private String botName = "BadugiBot",
			action = "";
	
	private String[][] botCards;

	
	/** 
	 * Constructor.
	 * @param host Server's IP address.
	 * @param port Server's port.
	 */
	public BadugiBot(String host, int port) {
		botName = "BOT";
		connectToServer(host, port);	
	}

	
	/** 
	 * Connects and checks if connection was successful.
	 * @param host Server's IP address.
	 * @param port Server's port.
	 */
	private void connectToServer(String host, int port) {
		try {
			botSocket = new Socket(host, port);

			reader = new DataInputStream(botSocket.getInputStream());
			writer = new DataOutputStream(botSocket.getOutputStream());

			Thread th = new Thread(this);
			th.start();

			sendRequest("setPlayerName " + botName);
			
			try {
				Thread.sleep(1000);
			} catch (Exception e) {}
			
			if (!connectionOk) {
				System.out.println(" > Bot cannot connect to server.");
				th.interrupt();
				System.exit(-1);
			} else {
				System.out.println(" > Bot successfully connected to server :)");
			}
		} catch (IOException e) {
			System.out.println(" > Cannot connect to '" + host + "' (port: " + port + ")");
			System.exit(-1);
		}
	}
	
	
	/** 
	 * Sends a single request to server. 
	 * @param message Request to send.
	 */
	private void sendRequest(String message) {
		try {
			writer.writeUTF(message);
		} catch (IOException e) {
			System.out.println(" ~ I can't send a request.");
		}
	}	
	
	
	/** 
	 * @param tab An array with splitted by ' ' server's response.
	 * Checks what moves are enabled to be made. 
	 */
	private void botPossibleMoves(String[] tab) {
		boolean check = false, bet = false, call = false, raise = false, allIn = false;
		boolean[] possibleActions = new boolean[]{check, bet, call, raise, allIn};
		int index = 0;
		
		for (int i = tab.length-5; i < tab.length; i++) {
			possibleActions[index] = Boolean.parseBoolean(tab[i]);
			
			index += 1;
		}
		
		botPossibleMoves = possibleActions;
	}
	
	
	/**
	 * Decides what move to make depending on strength of BOT's hand.
	 */
	private void itsBotsMove() {
		calculateChanceAndMaxBet();
		boolean[] m = botPossibleMoves;
		int amount = 0;
		
		Random rand = new Random();
		
		try {
			amount = rand.nextInt((int)(Math.round((botSuggestedMaxBet - botBet)*(botChance/100) + 0.5d))) + 1;
		} catch (Exception e) {
			amount = 1;
		}


		if (botChance < 4) {
			if (gameCurrentRound == 1)
				action = "fold";
			else
				action = "allIn";
			
		} else if (botChance < 10) {
			if (m[0])
				action = "check";
			else if (m[2] && gameMaxBet <= botChips*0.1)
				action = "call";
			else
				action = "allIn";
			
		} else if (botChance >= 10 && botChance < 20) {
			amount = (int)(Math.round(amount/4 + 0.5d));
			
			
			if (m[1])
				action = "bet 1";
			else if (m[0]) 
				action = "check";
			else if (m[2] && (gameMaxBet <= botSuggestedMaxBet || gameMaxBet <= botChips*0.1))
				action = "call";
			else
				action = "allIn";
			
		} else if (botChance >= 20 && botChance <= 35) {
			amount = (int)(Math.round(amount/2 + 0.5d));

			
			if (m[1])
				action = "bet " + amount;
			else if (m[3] && gameMaxBet + amount <= botSuggestedMaxBet)
				action = "raise " + amount;
			else if (m[3] && gameMaxBet + 1 <= botSuggestedMaxBet)
				action = "raise 1";
			else 
				action = "call";
			
		} else {
			int amount_half = (int)(Math.round(amount/2 + 0.5d));
			
			if (m[1])
				action = "bet " + amount;
			else if (m[3] && gameMaxBet + amount <= botSuggestedMaxBet)
				action = "raise " + amount;
			else if (m[3] && gameMaxBet + amount_half <= botSuggestedMaxBet)
				action = "raise " + amount_half;
			else if (m[3] && gameMaxBet + 1 <= botSuggestedMaxBet)
				action = "raise 1";
			else
				action = "call";
		}
		
		System.out.println(" > My next move is: " + action);
		
		
		try {
			sleep(600);
			sendRequest(action);
		} catch (Exception e) {
			sendRequest(action);
		}
	}
	
	
	/**
	 * Decides what cards to draw.
	 */
	private void drawCards() {
		String cardsToChange = "";
		

		if (botChance > 80) {
			try {
				sleep(300 + botID * 80);
			} catch (Exception e) {}

			sendRequest("dontDrawMyCards");
			return;

		} else if (Integer.parseInt(botCards[0][1]) > 8) {
			System.out.println(" > Drawing card: " + botCards[0][1] + "-" + botCards[0][2]);
			cardsToChange += " 1";
		}

		
		/*
		 * Porownujemy kazda karte z kazda. Jesli porownywana karta ma taka sama
		 * wartosc lub kolor jak wzorzec, ta wymieniamy ta wyzsza, ktora i tak jest usuwana
		 * przy koncowym porownywaniu kart wszystkich uzytkownikow.
		 */
		for (int i = 0; i < 3; i++) {
			for (int j = i+1; j < 4; j++) {
				if (botCards[i][1].equalsIgnoreCase(botCards[j][1]) || botCards[i][2].equalsIgnoreCase(botCards[j][2])) {
					if (!cardsToChange.contains(botCards[j][0])) {
						cardsToChange += " " + botCards[j][0];
						System.out.println(" > Drawing card: " + botCards[j][1] + "-" + botCards[j][2]);
					}
				}
			}
		}
		
		try {
			sleep(300 + botID * 80);
		} catch (Exception e) {}

		if (cardsToChange != "") {
			sendRequest("drawMyCards" + cardsToChange);
		} else {
			System.out.println(" > I don't draw any card.");
			sendRequest("dontDrawMyCards");
		}
	}
	
	
	/**
	 * Calculate BOT's hand strength and max amount of chips
	 * BOT can spend in current bet round.
	 */
	private void calculateChanceAndMaxBet() {
		botChance = botChance();
	
		if (botChance > 90) {
			botSuggestedMaxBet = botChips;
		} else {
			botSuggestedMaxBet = (int)(Math.round(botChips*botChance/100 + 0.5d));
		}
	}
	
	
	/**
	 * Calculate strength of BOT's strength.
	 * @return Strength as a double value.
	 */
	private double botChance() {
		double chance = 0;
		int[] cardsValues = new int[4];
		int[][] hands = new int[4][2];
		String[][] cards = new String[4][3];
		cards = botCards.clone();
		
		for (int k = 0; k <= 1; k++) {
			String values = "";
			String shapes = "";
			int[] numeric = new int[4];
			
			for (int i = 0; i <= 3; i++) {
				int min = 100;
				int minIndex = -1;
				
				for (int j = 0; j <= 3; j++) {
					if (cards[j][1] == null)
						continue;
					
					if (values.contains("["+cards[j][1]+"]") || shapes.contains(cards[j][2])) {
						cards[j] = new String[3];
					} else if (Integer.parseInt(cards[j][1]) < min) {
						min = Integer.parseInt(cards[j][1]);
						minIndex = j;
					}
				}
				
				if (minIndex == -1) {
					values += "[14]";
					numeric[i] = 14;
					continue;
				}
				
				values += "["+cards[minIndex][1]+"]";
				numeric[i] = Integer.parseInt(cards[minIndex][1]);
				shapes += cards[minIndex][2];
				cards[minIndex] = new String[3];
			}
			
			
			for (int l = 0; l <= 3; l++) {
				cards[l] = botCards[3-l];
			}
			
			hands[k] = numeric;
		}
		
		cardsValues = hands[0];
		
		for (int i = 3; i >= 0; i--) {
			if (hands[0][i] > hands[1][i])
				cardsValues = hands[1];
		}

		
		int[] best = new int[]{1,2,3,4};
		int[] worst = new int[]{13,14,14,14};
		int nullValues = 0;
		
		for (int i = 0; i <= 3; i++) {
			if (cardsValues[i] == 14) {
				nullValues += 1;
			}
			

			chance += ((worst[i] - cardsValues[i])*100) / (worst[i] - best[i]);
		}
		
		chance = Math.round(chance/(4*(nullValues+1)) + 0.5d);
		botChance = chance;
		return chance;
	}
	
	
	/**
	 * Decides what move to make if previous move was forbidden.
	 * @param act Previous move.
	 */
	private void botActionError(String act) {
		String[] actTab = act.split(" ");
		String actCmd = actTab[0];
		
		if (act.equals("allIn"))
			action = "fold";
		else if (act.equals("call"))
			action = "allIn";
		else if (act.equals("bet 1"))
			action = "check";
		else if (actCmd.equals("bet"))
			action = "bet 1";
		else if (act.equals("raise 1"))
			action = "call";
		else if (actCmd.equals("raise"))
			action = "raise 1";
		
		System.out.println(" > My next move is: " + action + " [previous action was forbidden]");
		
		try {
			sleep(600);
		} catch (Exception e) {}
		
		sendRequest(action);
	}


	/**
	 * It's where BOT listens and proceeds responses from server.
	 */
	public void run() {
		try {
			while (true) {
				String response = reader.readUTF();
				
				if (response == null)
					continue;
					
				String[] tab = response.split(" ");
				String cmd = tab[0];


				if (cmd.equalsIgnoreCase("numberOfPlayers")) {
					connectionOk = true;
					
				} else if (cmd.equalsIgnoreCase("yourID")) {
					botID = Integer.parseInt(tab[1]);
					
				} else if (cmd.equalsIgnoreCase("yourName")) {
					botName = tab[1];

				} else if (cmd.equalsIgnoreCase("yourCards")) {
					botCards = new String[4][3];
					
					for (int i = 1; i <= 4; i++) {
						String[] card = tab[i].split("-");
						botCards[i-1] = new String[]{
								i + "",
								card[0],
								card[1] + "-" + card[2] };
					}
					
					sortCards();
					
				} else if (cmd.equalsIgnoreCase("yourChips")) {
					botChips = Integer.parseInt(tab[1]);
					
					if (!isGameOpen) {
						botChipsOnStart = botChips;
						calculateChanceAndMaxBet();
					}
					
				} else if (cmd.equalsIgnoreCase("actionOk")) {
					
					if (action.equalsIgnoreCase("fold")) {
						botMovesEnabled = false;
						
					}
					
					action = "";
					
				} else if (cmd.equalsIgnoreCase("actionError")) {
					botActionError(action);
					
				} else if (cmd.equalsIgnoreCase("startOfTheGame")) {
					isGameOpen = true;

				} else if (cmd.equalsIgnoreCase("nextPlayerMove")) {
					if ((tab[1].equalsIgnoreCase(botID + "")) && (botMovesEnabled)) {
						botPossibleMoves(tab);
						itsBotsMove();
					}
					
				} else if (cmd.equalsIgnoreCase("newRound")) {
					gameCurrentRound = Integer.parseInt(tab[1]);
					gameMaxBet = 0;
					botBet = 0;
					
					if (gameCurrentRound % 2 == 1) {
						printMyCards();
					}
					
					if (gameCurrentRound == 1) {
						gameMaxBet = Integer.parseInt(tab[8]);
						
						calculateChanceAndMaxBet();
						System.out.println("   My hand strength: " + botChance + "%");
						
						if (tab[2].equalsIgnoreCase(botID + "")) {
							botPossibleMoves(tab);
							itsBotsMove();
						}
							
						if (tab[5].equals(botID + ""))
							botBet = Integer.parseInt(tab[6]);
						else if (tab[7].equals(botID + ""))
							botBet = Integer.parseInt(tab[8]);
						
					} else if (gameCurrentRound % 2 == 0) {
						drawCards();
					} else {
						calculateChanceAndMaxBet();
						
						System.out.println("   My hand strength: " + (int)botChance + "%");
						System.out.println("   I have $" + botSuggestedMaxBet + " to spend");
					}
					
				} else if (cmd.equalsIgnoreCase("disableMoves")) {
					botMovesEnabled = false;
					break;
					
				} else if (cmd.equalsIgnoreCase("endOfGame")) {
					isGameOpen = false;
					
					if (tab[1].equalsIgnoreCase("winner")) {
						if (tab[2].equals(botID + "")) {
							botChips = Integer.parseInt(tab[4]);
							System.out.println(" *** Yeah, I won! :)");
						} else {
							System.out.println(" *** Meeh, maybe next time...");
						}
							
					} else {
						System.out.println(" *** Wow, it's a tie!");
					}

				} else if (cmd.equalsIgnoreCase("playersCanPlayAgain")) {
					boolean canIPlay = false;
					
					for (int i = 1; i < tab.length; i++) {
						if (tab[i].equals(botID + "")) {
							canIPlay = true;
							break;	
						}
					}
					
					try {
						sleep(300 + botID*100);
					} catch (Exception e) {}
					
					
					if (canIPlay) {
						sendRequest("nextGameYes"); 
					} else {
						sendRequest("nextGameNo"); 
						System.exit(0);
					}
					
				} else if (cmd.equalsIgnoreCase("tableUpdate")) {

					if (tab[1].equals("potAndMaxBet")) {
						gameMaxBet = Integer.parseInt(tab[3]);
						
					} else if (tab[1].equals("yourBet")) {
						botTotalBet = Integer.parseInt(tab[2]);
						botChips = botChipsOnStart - botTotalBet;
					}
				}
			}
		} catch (IOException e) {}
	}

	
	/**
	 * Displays all BOT's cards in terminal.
	 */
	private void printMyCards() {
		System.out.println("   My cards:");
		
		for (int i = 0; i <= 3; i++) {
			System.out.println(" * " + botCards[i][1] + "-" + botCards[i][2] + " ");
		}
	}
	
	
	/**
	 * Sort BOT's cards starting with the strongest card.
	 */
	private void sortCards() {
		String[][] sortedCards = new String[4][3];
		int j = 0, minIndex = 0;

		for (int i = 0; i < botCards.length; i++) {
			int min = 15;
			
			for (j = 0; j < botCards.length; j++) {
				if (botCards[j][0] != null) {
					if (Integer.parseInt(botCards[j][1]) < min) {
						min = Integer.parseInt(botCards[j][1]);
						minIndex = j;
					}
				}
			}
			
			sortedCards[i] = botCards[minIndex];
			botCards[minIndex] = new String[3];
		}
		
		botCards = sortedCards;	
	}
	

	/**
	 * Main method.
	 * @param args Optional parameters: [0] => host, [1] => port.
	 */
	public static void main(String[] args) {
		String host;
		int port;

		try {
			host = args[0];
			port = Integer.parseInt(args[1]);
		} catch (Exception e) {
			port = 4444;
			host = "localhost";
		}

		new BadugiBot(host, port);
	}
}